package com.koudai.finance.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Description: redis工具类
 * @author: ls
 * @Date: 2020/7/12 9:37
 */
@Component
@Slf4j
public class RedisUtil {

    /**
     * 失效时间一天
     */
    public final int EXPIRE_DAY = 3600 * 24;
    /**
     * 失效时间半天
     */
    public final int EXPIRE_HELF_DAY = 3600 * 12;
    /**
     * 失效时间一小时
     */
    public final int EXPIRE_HOUR = 3600;
    /**
     * 失效时间半小时
     */
    public final int EXPIRE_HALF_HOUR = 1800;
    /**
     * 失效时间十分钟
     */
    public final int EXPIRE_TEN_MIN = 600;
    /**
     * 失效时间一分钟
     */
    public final int EXPIRE_ONE_MIN = 60;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * @param key
     * @param value
     * @throws
     */
    public void set(String key, String value, int time, TimeUnit timeUnit) {
        try {
            stringRedisTemplate.opsForValue().set(key, value, time, timeUnit);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * @param key
     * @param value void
     * @throws
     * @Description: 设置单个值
     */
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    /**
     * @param key
     * @param obj void
     * @throws
     * @Description: 设置对象
     */
    public void setObject(String key, Object obj) {
        set(key, JSON.toJSONString(obj));
    }

    /**
     * @param key
     * @param obj
     * @throws
     * @Description: 设置对象，在seconds秒后失效
     */
    public void setObject(String key, Object obj, int time, TimeUnit timeUnit) {
        set(key, JSON.toJSONString(obj), time, timeUnit);
    }

    /**
     * @param key
     * @return String
     * @throws
     * @Description: 获取单个值
     */
    public String get(String key) {
        String result = null;
        try {
            result = stringRedisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * @param key   缓存的key
     * @param clazz 缓存的对象类型
     * @return T
     * @throws
     * @Description: 根据key和对象类型，获取对象
     */
    public <T> T getObject(String key, Class<T> clazz) {
        String json = get(key);
        if (!Strings.isNullOrEmpty(json)) {
            return JSON.parseObject(json, clazz);
        }
        return null;
    }

    /**
     * @param key       缓存 的key
     * @param keyType   map的key类型
     * @param valueType map的value类型
     * @return Map<K, V>
     * @throws
     * @Description: 获取map
     */
    public <K, V> Map<K, V> getMap(String key, Class<K> keyType, Class<V> valueType) {
        String json = get(key);
        if (!Strings.isNullOrEmpty(json)) {
            return JSON.parseObject(json, new TypeReference<Map<K, V>>(keyType, valueType) {
            });
        }
        return null;
    }

    /**
     * @param key   缓存的key
     * @param clazz 缓存的对象类型
     * @return 对象列表 List<T>
     * @throws
     * @Description: 获取对象列表
     */
    public <T> List<T> getList(String key, Class<T> clazz) {
        String value = get(key);
        if (!Strings.isNullOrEmpty(value)) {
            return JSON.parseArray(value, clazz);
        }
        return null;
    }

    /**
     * @param key     缓存的对象key
     * @param objList 缓存的对象列表
     *                void
     * @throws
     * @Description: 设置对象列表, seconds秒后失效
     */
    public void setList(String key, List<?> objList, int seconds) {
        setList(key, objList);
        expire(key, seconds);
    }

    /**
     * @param key     缓存的对象key
     * @param objList 缓存的对象列表
     *                void
     * @throws
     * @Description: 设置对象列表
     */
    public void setList(String key, List<?> objList) {
        if (CollectionUtils.isNotEmpty(objList)) {
            String json = JSON.toJSONString(objList);
            set(key, json);
        }
    }

    /**
     * @param key
     * @param value
     * @return Boolean
     * @throws
     * @Description: 如果key不存在，则set操作，返回true，否则返回false。
     */
    public Boolean setnx(String key, String value) {
        Boolean result = null;
        try {
            result = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * Description: 如果key不存在，则set操作，返回true，否则返回false。(带超时时间)
     *
     * @param
     * @CreateDate 2019/8/29 19:10
     */
    public Boolean setnx(String key, String value, int seconds) {
        Boolean result = false;
        try {
            result = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
            if (result) {
                stringRedisTemplate.expire(key, seconds, TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * @Description: redis加锁
     * @Param:
     * @Author: ls
     * @Date: 2020/03/20 18:21
     */
    public Boolean lock(String key, String value, int seconds) {
        return setnx(key, value, seconds);
    }

    /**
     * @Description: redis释放锁
     * @Param:
     * @Author: ls
     * @Date: 2020/03/20 18:21
     */
    public void releaseLock(String key) {
        del(key);
    }

    /**
     * 判断key是否存在
     *
     * @param key
     * @return
     */
    public Boolean exists(String key) {
        Boolean result = false;
        try {
            result = stringRedisTemplate.hasKey(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * 获取key所存储的值的类型
     *
     * @param key
     * @return
     */
    public String type(String key) {
        String result = null;
        try {
            result = stringRedisTemplate.type(key).code();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * 在某段时间后失效
     *
     * @param key
     * @return
     */
    public void expire(String key, int seconds) {
        try {
            stringRedisTemplate.expire(key, seconds, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 在某个时间点失效
     *
     * @param key
     * @return
     */
    public void expireAt(String key, Date date) {
        try {
            stringRedisTemplate.expireAt(key, date);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * @return
     */
    public Long ttl(String key) {
        Long result = null;
        try {
            result = stringRedisTemplate.getExpire(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * @return
     */
    public String getSet(String key, String value) {
        String result = null;
        try {
            result = stringRedisTemplate.opsForValue().getAndSet(key, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * @return
     */
    public Long incrBy(String key, long integer) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForValue().increment(key, integer);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * @return
     */
    public Long incr(String key) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForValue().increment(key, 1);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * @return
     */
    public Integer append(String key, String value) {
        Integer result = null;
        try {
            result = stringRedisTemplate.opsForValue().append(key, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * hash 操作
     *
     * @param key
     * @param field
     * @param value
     */
    public void hset(String key, String field, String value) {
        try {
            HashOperations<String, String, String> hashOps = stringRedisTemplate.opsForHash();
            hashOps.put(key, field, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public String hget(String key, String field) {
        String result = null;
        try {
            HashOperations<String, String, String> hashOps = stringRedisTemplate.opsForHash();
            result = (String) hashOps.get(key, field);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Boolean hsetnx(String key, String field, String value) {
        Boolean result = null;
        try {
            HashOperations<String, String, String> hashOps = stringRedisTemplate.opsForHash();
            result = hashOps.putIfAbsent(key, field, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public void hmset(String key, Map<String, String> hash) {
        try {
            HashOperations<String, String, String> hashOps = stringRedisTemplate.opsForHash();
            hashOps.putAll(key, hash);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public List<String> hmget(String key, Collection<String> fields) {
        List<String> result = null;
        try {
            HashOperations<String, String, String> hashOps = stringRedisTemplate.opsForHash();
            result = hashOps.multiGet(key, fields);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Long hincrBy(String key, String field, long value) {
        Long result = null;
        try {
            HashOperations<String, String, String> hashOps = stringRedisTemplate.opsForHash();
            result = hashOps.increment(key, field, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);

        }
        return result;
    }

    public Boolean hexists(String key, String field) {
        Boolean result = false;
        try {
            HashOperations<String, String, String> hashOps = stringRedisTemplate.opsForHash();
            result = hashOps.hasKey(key, field);
        } catch (Exception e) {
            log.error(e.getMessage(), e);

        }
        return result;
    }

    public void del(String key) {
        try {
            stringRedisTemplate.delete(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public void hdel(String key, Object... fields) {
        try {
            HashOperations<String, String, String> hashOps = stringRedisTemplate.opsForHash();
            hashOps.delete(key, fields);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public Long hlen(String key) {
        Long result = null;
        try {
            HashOperations<String, String, String> hashOps = stringRedisTemplate.opsForHash();
            result = hashOps.size(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<String> hkeys(String key) {
        Set<String> result = null;
        try {
            HashOperations<String, String, String> hashOps = stringRedisTemplate.opsForHash();
            result = hashOps.keys(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public List<String> hvals(String key) {
        List<String> result = null;
        try {
            HashOperations<String, String, String> hashOps = stringRedisTemplate.opsForHash();
            result = hashOps.values(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Map<String, String> hgetAll(String key) {
        Map<String, String> result = null;
        try {
            HashOperations<String, String, String> hashOps = stringRedisTemplate.opsForHash();
            result = hashOps.entries(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * list 操作，l表示 list或 left, r表示right
     *
     * @param key
     * @param value
     * @return
     */
    public Long rpush(String key, String value) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForList().rightPush(key, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }


    public void setrange(String key, long offset, String value) {
        try {
            stringRedisTemplate.opsForList().set(key, offset, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public List<String> getrange(String key, long startOffset, long endOffset) {
        List<String> result = null;

        try {
            result = stringRedisTemplate.opsForList().range(key, startOffset, endOffset);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Long lpush(String key, String value) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForList().leftPush(key, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * @param key
     * @param value
     * @return
     */
    public Long lpushAll(String key, List<String> value) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForList().leftPushAll(key, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }


    public Long llen(String key) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForList().size(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public List<String> lrange(String key, long start, long end) {
        List<String> result = null;
        try {
            result = stringRedisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public void ltrim(String key, long start, long end) {
        try {
            stringRedisTemplate.opsForList().trim(key, start, end);

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public String lindex(String key, long index) {
        String result = null;
        try {
            result = stringRedisTemplate.opsForList().index(key, index);

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public void lset(String key, long index, String value) {
        try {
            stringRedisTemplate.opsForList().set(key, index, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public Long lrem(String key, long index, String value) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForList().remove(key, index, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public String lpop(String key) {
        String result = null;
        try {
            result = stringRedisTemplate.opsForList().leftPop(key);

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public String rpop(String key) {
        String result = null;
        try {
            result = stringRedisTemplate.opsForList().rightPop(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * set 操作
     *
     * @param key
     * @param member
     * @return
     */
    public Long sadd(String key, String member) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForSet().add(key, member);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<String> smembers(String key) {
        Set<String> result = null;
        try {
            result = stringRedisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Long srem(String key, String member) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForSet().remove(key, member);
        } catch (Exception e) {
            log.error(e.getMessage(), e);

        }
        return result;
    }

    public String spop(String key) {
        String result = null;
        try {
            result = stringRedisTemplate.opsForSet().pop(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }


    /**
     * @param key
     * @return
     */
    public Long scard(String key) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Boolean sismember(String key, String member) {
        Boolean result = null;
        try {
            result = stringRedisTemplate.opsForSet().isMember(key, member);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public String srandmember(String key) {
        String result = null;
        try {
            result = stringRedisTemplate.opsForSet().randomMember(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * zset 操作
     *
     * @param key
     * @param score
     * @param member
     * @return
     */
    public Boolean zadd(String key, double score, String member) {
        Boolean result = null;
        try {
            result = stringRedisTemplate.opsForZSet().add(key, member, score);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<String> zRange(String key, int start, int end) {
        Set<String> result = null;
        try {
            result = stringRedisTemplate.opsForZSet().range(key, start, end);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Long zRem(String key, Object... member) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForZSet().remove(key, member);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Double zincrby(String key, double score, String member) {
        Double result = null;
        try {
            result = stringRedisTemplate.opsForZSet().incrementScore(key, member, score);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Long zrank(String key, String member) {
        Long result = null;
        try {

            result = stringRedisTemplate.opsForZSet().rank(key, member);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Long zrevrank(String key, String member) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForZSet().reverseRank(key, member);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<String> zrevrange(String key, int start, int end) {
        Set<String> result = null;
        try {
            result = stringRedisTemplate.opsForZSet().reverseRange(key, start, end);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<TypedTuple<String>> zrangeWithScores(String key, int start, int end) {
        Set<TypedTuple<String>> result = null;
        try {
            result = stringRedisTemplate.opsForZSet().rangeWithScores(key, start, end);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<TypedTuple<String>> zrevrangeWithScores(String key, int start, int end) {
        Set<TypedTuple<String>> result = null;
        try {
            result = stringRedisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Long zcard(String key) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForZSet().zCard(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Double zscore(String key, String member) {
        Double result = null;
        try {
            result = stringRedisTemplate.opsForZSet().score(key, member);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Long zcount(String key, double min, double max) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForZSet().count(key, min, max);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<String> zrangeByScore(String key, double min, double max) {
        Set<String> result = null;
        try {
            result = stringRedisTemplate.opsForZSet().rangeByScore(key, min, max);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<String> zrevrangeByScore(String key, double max, double min) {
        Set<String> result = null;
        try {
            result = stringRedisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<String> zrangeByScore(String key, double min, double max, int offset, int count) {
        Set<String> result = null;
        try {
            result = stringRedisTemplate.opsForZSet().rangeByScore(key, min, max, offset, count);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<String> zrevrangeByScore(String key, double max, double min, int offset, int count) {
        Set<String> result = null;
        try {
            result = stringRedisTemplate.opsForZSet().reverseRangeByScore(key, min, max, offset, count);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<TypedTuple<String>> zrangeByScoreWithScores(String key, double min, double max) {
        Set<TypedTuple<String>> result = null;
        try {
            result = stringRedisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<TypedTuple<String>> zrevrangeByScoreWithScores(String key, double max, double min) {
        Set<TypedTuple<String>> result = null;
        try {
            result = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, min, max);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<TypedTuple<String>> zrangeByScoreWithScores(String key, double min, double max, int offset, int count) {
        Set<TypedTuple<String>> result = null;
        try {
            result = stringRedisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max, offset, count);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Set<TypedTuple<String>> zrevrangeByScoreWithScores(String key, double max, double min, int offset, int count) {
        Set<TypedTuple<String>> result = null;
        try {
            result = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, min, max, offset, count);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    public Long zremrangeByScore(String key, double min, double max) {
        Long result = null;
        try {
            result = stringRedisTemplate.opsForZSet().removeRangeByScore(key, min, max);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * redis计数器
     *
     * @param keyName
     * @param by
     * @param liveTime 失效时间，0则表示没有失效时间 秒为单位
     * @return
     */
    public long increment(String keyName, long by, long liveTime) {
        long value = 1;
        try {
            value = stringRedisTemplate.opsForValue().increment(keyName, by);
            if (liveTime > 0 && value == 1) {
                stringRedisTemplate.expire(keyName, liveTime, TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            //自增异常或者设置失效时间异常均删除该key
            if (liveTime > 0) {
                del(keyName);
            }
            log.error("redis increment Exception:", e);
        } finally {
            //一定时间内key自增大于5或者key是无期key则删掉该key
            if (liveTime > 0 && value >= 5) {
                //获取失效时间 -1代表无期 ,-2代表key不存在 ,>=0代表存在有效期
                Long l = ttl(keyName);
                log.error(">>>>>>finally l<<<<<" + l);
                if (l == -1) {
                    del(keyName);
                }
            }
        }
        return value;
    }

    public void publish(String key, String content) {
        try {
            stringRedisTemplate.convertAndSend(key, content);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}
